package cn.jbolt.core.db.datasource;

import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.Setting;
import cn.jbolt.core.base.config.JBoltConfig;
import cn.jbolt.core.base.config.JBoltExtendDatabaseConfig;
import cn.jbolt.core.bean.Option;
import cn.jbolt.core.bean.OptionBean;
import cn.jbolt.core.cache.JBoltCacheKit;
import cn.jbolt.core.db.datasource.database.table.JBoltDatabaseColumn;
import cn.jbolt.core.db.datasource.database.table.JBoltDatabaseTable;
import cn.jbolt.core.db.dialect.JBoltDMDialect;
import cn.jbolt.core.db.dialect.JBoltMysqlDialect;
import cn.jbolt.core.db.dialect.JBoltOracleDialect;
import cn.jbolt.core.db.dialect.JBoltPostgresqlDialect;
import cn.jbolt.core.db.sql.DBType;
import cn.jbolt.core.db.sql.Sql;
import cn.jbolt.core.enjoy.directive.ColumnTypeToDirective;
import cn.jbolt.core.gen.JBoltMetaBuilder;
import cn.jbolt.core.gen._JBoltOracleMetaBuilder;
import cn.jbolt.core.gen._JBoltSqlServerMetaBuilder;
import cn.jbolt.core.model.DatasourceFilter;
import cn.jbolt.core.model.base.JBoltModelConfig;
import cn.jbolt.core.util.JBoltArrayUtil;
import com.jfinal.aop.Aop;
import com.jfinal.kit.Kv;
import com.jfinal.kit.Ret;
import com.jfinal.kit.StrKit;
import com.jfinal.plugin.activerecord.Config;
import com.jfinal.plugin.activerecord.DbKit;
import com.jfinal.plugin.activerecord.dialect.SqlServerDialect;
import com.jfinal.plugin.activerecord.generator.ColumnMeta;
import com.jfinal.plugin.activerecord.generator.TableMeta;

import javax.sql.DataSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * JBolt 内部数据源工具类
 * @ClassName:  JBoltDataSourceKit
 * @author: JFinal学院-小木 QQ：909854136
 * @date:   2021年9月25日
 */
public class JBoltDataSourceUtil {
	public static final JBoltDataSourceUtil me = new JBoltDataSourceUtil();
	private List<JBoltDatasource> datasources ;
	private JBoltDataSourceUtil() {
		this.datasources = new ArrayList<JBoltDatasource>();
	}

	/**
	 * 初始化
	 */
	public void init() {
		this.datasources = new ArrayList<JBoltDatasource>();
		Optional<List<Config>> cop = Optional.of(getAllConfigs());
		cop.ifPresent(configs -> configs.forEach(config -> {
			datasources.add(convertToJBoltDatasource(config));
		}));
	}

	public void clear() {
		if(datasources.size()>0) {
			datasources.clear();
		}
	}

	public void reload() {
		clear();
		init();
	}
	/**
	 * 重新加载指定数据源的tables
	 * @param dataSourceName
	 */
	public Ret reloadDatasourceTables(String dataSourceName) {
		JBoltDatasource datasource = getJBoltDatasource(dataSourceName);
		if(datasource == null) {
			return Ret.fail("指定的数据源不存在");
		}
		datasource.clearTables();
		initJBoltDatasourceTables(datasource);
		return Ret.ok();
	}

	/**
	 * 获取所有配置
	 * @return
	 */
	public List<Config> getAllConfigs(){
		return DbKit.getConfigSet().stream()
				.map(Map.Entry::getValue)
				.collect(Collectors.toList());
	}

	/**
	 * 获取所有配置
	 * @return
	 */
	public List<JBoltDatasource> getAllDatasource(){
		if(datasources.size() == 0) {
			init();
		}
		return datasources;
	}

	/**
	 * 转为JBoltDataSource
	 * @param config
	 * @return
	 */
	private  JBoltDatasource convertToJBoltDatasource(Config config) {
		if(config.getName().equals(DbKit.MAIN_CONFIG_NAME)) {
			return convertToJBoltMainDataSource(config);
		}
		Setting setting = JBoltExtendDatabaseConfig.me().getSetting();
		if(setting != null) {
			return convertToJBoltExtendDataSource(config,setting.getSetting(config.getName()));
		}
		return null;
	}
	/**
	 * 根据主数据源配置转换
	 * @param config
	 * @return
	 */
	private  JBoltDatasource convertToJBoltMainDataSource(Config config) {
		JBoltDatasource datasource = new JBoltDatasource();
		datasource.setIsMainDatasource(true);
		datasource.setConfig(config);
		datasource.setName(config.getName());
		datasource.setDbType(JBoltConfig.MAIN_DB_TYPE);
		datasource.setDbName(JBoltConfig.MAIN_DB_NAME);
		datasource.setDbSchema(JBoltConfig.MAIN_DB_SCHEMA);
		datasource.setRemark(JBoltConfig.prop.get("remark", "主"));
		datasource.setJdbcUrl(JBoltConfig.prop.get("jdbc_url"));
		datasource.setUser(JBoltConfig.prop.get("user"));
//		datasource.setPassword(JBoltConfig.prop.get("password"));
		datasource.setIdGenMode(JBoltConfig.prop.get("id_gen_mode"));
		datasource.setModelPackages(JBoltArrayUtil.listFrom(JBoltConfig.prop.get("model_package"), ","));
		processDataSourceFilter(datasource);
		return datasource;
	}
	/**
	 * 处理数据源 filter的同步工作
	 * @param datasource
	 */
	public void processDataSourceFilter(JBoltDatasource datasource) {
		DatasourceFilterService service = Aop.get(DatasourceFilterService.class);
		DatasourceFilter filter = service.getCacheByKey(datasource.getName());
		String name = datasource.getName()+"["+datasource.getRemark()+"]";
		if(filter == null) {
			filter = new DatasourceFilter();
			filter.setName(name);
			filter.setConfigName(datasource.getName());
			Ret ret = service.save(filter);
			if(ret.isOk()) {
				datasource.setDatasourceFilter(filter);
			}else {
				datasource.setDatasourceFilter(null);
			}
		}else {
			if(!filter.getName().equals(name)) {
				filter.setName(name);
				service.update(filter);
			}
			datasource.setDatasourceFilter(filter);
		}

	}

	/**
	 * 根据扩展数据源 转化
	 * @param config
	 * @param setting
	 * @return
	 */
	private  JBoltDatasource convertToJBoltExtendDataSource(Config config,Setting setting) {
		if(setting == null) {
			return null;
		}
		JBoltDatasource datasource = new JBoltDatasource();
		datasource.setIsMainDatasource(false);
		datasource.setConfig(config);
		datasource.setName(config.getName());
		datasource.setDbType(setting.get("db_type"));
		datasource.setDbName(setting.get("db_name"));
		datasource.setDbSchema(setting.get("db_schema"));
		datasource.setRemark(setting.getStr("remark", "扩展"));
		datasource.setJdbcUrl(setting.get("jdbc_url"));
		datasource.setUser(setting.get("user"));
//		datasource.setPassword(setting.get("password"));
		datasource.setIdGenMode(setting.get("id_gen_mode"));
		datasource.setModelPackages(JBoltArrayUtil.listFrom(setting.get("model_package"), ","));
		processDataSourceFilter(datasource);
		return datasource;
	}

	/**
	 * 获取所有数据源转Option list
	 * @return
	 */
	public List<Option> getAllOptions(){
		return datasources
		     .stream()
		     .map((e)->new OptionBean(e.getOptionText(),e.getName()))
		     .collect(Collectors.toList());
	}

	/**
	 * 根据name获取到配置
	 * @param name
	 * @return
	 */
	public Config getConfig(String name) {
		if(StrKit.isBlank(name)) {return null;}
		return DbKit.getConfig(name);
	}

	/**
	 * 获取主数据源配置
	 * @return
	 */
	public Config getMainConfig() {
		return DbKit.getConfig(DbKit.MAIN_CONFIG_NAME);
	}

	/**
	 * 根据name获取到datasource
	 * @param name
	 * @return
	 */
	public DataSource getDatasource(String name) {
		if(StrKit.isBlank(name)) {return null;}
		Optional<Config> op = Optional.of(DbKit.getConfig(name));
		return op.map(Config::getDataSource).orElse(null);
	}
	/**
	 * 根据name获取到jboltdatasource
	 * @param name
	 * @return
	 */
	public JBoltDatasource getJBoltDatasource(String name) {
		if(StrKit.isBlank(name)) {return null;}
		return datasources
				.stream()
				.filter(ds->name.equals(ds.getName()))
				.findFirst().orElse(null);
	}

	/**
	 * 获取所有扩展数据源配置
	 * @return
	 */
	public List<Config> getExtendConfigs() {
		return DbKit.getConfigSet()
				.stream()
				.filter((e)->(!e.getKey().equals(DbKit.MAIN_CONFIG_NAME)))
				.map(Map.Entry::getValue)
				.collect(Collectors.toList());
	}

	/**
	 * 获取所有扩展数据源配置
	 * @return
	 */
	public List<JBoltDatasource> getExtendJBoltDataSources() {
		return datasources
				    .stream()
					.filter((e)->(!e.getName().equals(DbKit.MAIN_CONFIG_NAME)))
					.collect(Collectors.toList());
	}

	/**
	 * 获取指定数据源下的表  option list
	 * @param datasourceName
	 * @param keywords
	 * @param limit
	 * @return
	 */
	public List<Option> getTableOptions(String datasourceName, String keywords, int limit) {
		Stream<TableMeta> stream = getTableStream(datasourceName, keywords, limit);
		if(stream == null) {return null;}
		return stream.map((tm)->new OptionBean(tm.name+(StrKit.isBlank(tm.remarks)?"":("["+tm.remarks+"]")),tm.name)).collect(Collectors.toList());
	}
	/**
	 * 获取指定数据源下的表  Autocomplete Data list
	 * @param datasourceName
	 * @param keywords
	 * @param limit
	 * @return
	 */
	public List<Kv> getTableNameRemarkKvs(String datasourceName, String keywords, int limit) {
		Stream<TableMeta> stream = getTableStream(datasourceName, keywords, limit);
		if(stream == null) {return null;}
		return stream.map((tm)->Kv.by("name",tm.name).set("remark", tm.remarks)).collect(Collectors.toList());
	}
	/**
	 * 获取指定数据源下的表  Autocomplete Data list
	 * @param datasourceName
	 * @param keywords
	 * @param limit
	 * @return
	 */
	public List<Kv> getTableNameRemarkFullKvs(String datasourceName, String keywords, int limit) {
		Stream<TableMeta> stream = getTableStream(datasourceName, keywords, limit);
		if(stream == null) {return null;}
		return stream.map((tm)->Kv.by("name",tm.name).set("prefix",processTablePrefix(tm.name)).set("remark", tm.remarks).set("fullName",tm.name+(StrKit.isBlank(tm.remarks)?"":("["+tm.remarks+"]")))).collect(Collectors.toList());
	}

	/**
	 * 获取表明前缀
	 * @param name
	 * @return
	 */
	private String processTablePrefix(String name) {
		if(name.contains("_")){
			return name.substring(0,name.indexOf("_")+1);
		}
		return null;
	}

	/**
	 * 获取指定数据源下的表tableMeta list
	 * @param datasourceName
	 * @param keywords
	 * @param limit
	 * @return
	 */
	public List<TableMeta> getTableMetas(String datasourceName, String keywords, int limit) {
		Stream<TableMeta> stream = getTableStream(datasourceName, keywords, limit);
		if(stream == null) {return null;}
		return stream.collect(Collectors.toList());
	}

	/**
	 * 获取指定数据源下的表tableMeta Stream
	 * @param datasourceName
	 * @param keywords
	 * @param limit
	 * @return
	 */
	public Stream<TableMeta> getTableStream(String datasourceName, String keywords, int limit) {
		JBoltDatasource datasource= getJBoltDatasource(datasourceName);
		if(datasource == null || limit == 0 || limit < -1) {return null;}
		if(!datasource.hasTables()) {
			initJBoltDatasourceTables(datasource);
		}
		if(!datasource.hasTables()) {
			return null;
		}
		Stream<TableMeta> tableStream = datasource.getTables().stream();
		if(StrKit.notBlank(keywords)) {
			tableStream = tableStream.parallel().filter(tb->((tb.name.toLowerCase().contains(keywords.toLowerCase()))||(StrKit.notBlank(tb.remarks)&&tb.remarks.toLowerCase().contains(keywords.toLowerCase())))).limit(limit);
		}else {
			tableStream = tableStream.limit(limit);
		}
		return tableStream.sorted((tb1,tb2)->tb1.name.compareTo(tb2.name));
	}
	/**
	 * 初始化读取一个数据源的可以读取到的表
	 * @param jboltDatasource
	 */
	public void initJBoltDatasourceTables(JBoltDatasource jboltDatasource) {
		DataSource dataSource = jboltDatasource.getConfig().getDataSource();
		JBoltMetaBuilder metaBuilder = null;
		String dbSchema=jboltDatasource.getDbSchema();
			switch (jboltDatasource.getDbType()) {
				case DBType.MYSQL:
					metaBuilder = new JBoltMetaBuilder(dataSource);
					metaBuilder.setDialect(new JBoltMysqlDialect());
					if(StrKit.notBlank(dbSchema)) {
						metaBuilder.setSchemaPattern(dbSchema);
					}
					break;
				case DBType.ORACLE:
					metaBuilder = new _JBoltOracleMetaBuilder(dataSource);
					metaBuilder.setDialect(new JBoltOracleDialect());
					if(StrKit.notBlank(dbSchema)) {
						metaBuilder.setSchemaPattern(dbSchema);
					}
					break;
				case DBType.SQLSERVER:
					metaBuilder = new _JBoltSqlServerMetaBuilder(dataSource);
					metaBuilder.setDialect(new SqlServerDialect());
					if(StrKit.notBlank(dbSchema)) {
						metaBuilder.setSchemaPattern(dbSchema);
					}
					break;
				case DBType.POSTGRESQL:
					metaBuilder = new JBoltMetaBuilder(dataSource);
					metaBuilder.setDialect(new JBoltPostgresqlDialect());
					String schemaPattern=getPostGresqlSchema(jboltDatasource.getJdbcUrl());
					if(StrKit.notBlank(schemaPattern)){
						metaBuilder.setSchemaPattern(schemaPattern);
					}else if(StrKit.notBlank(dbSchema)) {
						metaBuilder.setSchemaPattern(dbSchema);
					}
					break;
				case DBType.DM:
					metaBuilder = new JBoltMetaBuilder(dataSource);
					metaBuilder.setDialect(new JBoltDMDialect());
					if(StrKit.notBlank(dbSchema)) {
						metaBuilder.setSchemaPattern(dbSchema);
					}
					break;
			}
			if(metaBuilder==null) {
				throw new RuntimeException("目前只支持Mysql、Oracle、SqlServer、Postgresql、达梦 5个数据库");
			}
			metaBuilder.setGenerateRemarks(true);
			metaBuilder.skip(jboltDatasource.getTableSkipPredicate());
			List<TableMeta> tableMetas = metaBuilder.build();
			jboltDatasource.setTables(tableMetas);
	}

	private String getPostGresqlSchema(String jdbcUrl) {
		if(jdbcUrl.contains("currentSchema")) {
			String schemaname=jdbcUrl.substring(jdbcUrl.lastIndexOf("currentSchema")+13).replace("=", "").trim();
			if(StrUtil.isNotBlank(schemaname)) {
				return schemaname;
			}
		}
		return null;
	}

	public List<TableMeta> getAllTableMetas(String datasource){
		return getAllTableMetas(datasource,null);
	}
	public List<TableMeta> getAllTableMetas(String datasource,Predicate<String> predicate){
		JBoltDatasource jBoltDatasource = getJBoltDatasource(datasource);
		return getAllTableMetas(jBoltDatasource,predicate);
	}
	public List<TableMeta> getAllTableMetas(JBoltDatasource jboltDatasource) {
		return getAllTableMetas(jboltDatasource,null);
	}
	public List<TableMeta> getAllTableMetas(JBoltDatasource jboltDatasource,Predicate<String> predicate) {
		if(jboltDatasource == null){
			return null;
		}
		//先清理缓存
		removeAllTablesCache(jboltDatasource.getName());
		//再读取
		DataSource dataSource = jboltDatasource.getConfig().getDataSource();
		JBoltMetaBuilder metaBuilder = null;
		String dbSchema=jboltDatasource.getDbSchema();
		switch (jboltDatasource.getDbType()) {
			case DBType.MYSQL:
				metaBuilder = new JBoltMetaBuilder(dataSource);
				metaBuilder.setDialect(new JBoltMysqlDialect());
				if(StrKit.notBlank(dbSchema)) {
					metaBuilder.setSchemaPattern(dbSchema);
				}
				break;
			case DBType.ORACLE:
				metaBuilder = new _JBoltOracleMetaBuilder(dataSource);
				metaBuilder.setDialect(new JBoltOracleDialect());
				if(StrKit.notBlank(dbSchema)) {
					metaBuilder.setSchemaPattern(dbSchema);
				}
				break;
			case DBType.SQLSERVER:
				metaBuilder = new _JBoltSqlServerMetaBuilder(dataSource);
				metaBuilder.setDialect(new SqlServerDialect());
				if(StrKit.notBlank(dbSchema)) {
					metaBuilder.setSchemaPattern(dbSchema);
				}
				break;
			case DBType.POSTGRESQL:
				metaBuilder = new JBoltMetaBuilder(dataSource);
				metaBuilder.setDialect(new JBoltPostgresqlDialect());
				String schemaPattern=getPostGresqlSchema(jboltDatasource.getJdbcUrl());
				if(StrKit.notBlank(schemaPattern)){
					metaBuilder.setSchemaPattern(schemaPattern);
				}else if(StrKit.notBlank(dbSchema)) {
					metaBuilder.setSchemaPattern(dbSchema);
				}
				break;
			case DBType.DM:
				metaBuilder = new JBoltMetaBuilder(dataSource);
				metaBuilder.setDialect(new JBoltDMDialect());
				if(StrKit.notBlank(dbSchema)) {
					metaBuilder.setSchemaPattern(dbSchema);
				}
				break;
		}
		if(metaBuilder==null) {
			throw new RuntimeException("目前只支持Mysql、Oracle、SqlServer、Postgresql、达梦 5个数据库");
		}
		metaBuilder.setGenerateRemarks(true);
		if(predicate!=null){
			metaBuilder.skip(predicate);
		}else{
			metaBuilder.skip(jboltDatasource._getDevDocTableSkipPredicate());
		}
		List<TableMeta> tableMetas = metaBuilder.build();
		if(tableMetas!=null && tableMetas.size()>0){
			tableMetas.forEach(tableMeta -> {
				Class clazz = JBoltModelConfig.me.getModelClassByTable(tableMeta.name);
				if(clazz!=null){
					tableMeta.modelName = clazz.getName();
					tableMeta.baseModelName = tableMeta.modelName.replace(tableMeta.modelName,"Base"+tableMeta.modelName);
				}

			});
		}
		return tableMetas;
	}
	public List<JBoltDatabaseTable> getAllTablesFromCache(String datasource){
		return getAllTablesFromCache(datasource,null);
	}
	public List<JBoltDatabaseTable> getAllTablesFromCache(String datasource,Predicate<String> predicate){
		JBoltDatasource jBoltDatasource = getJBoltDatasource(datasource);
		return getAllTablesFromCache(jBoltDatasource,predicate);
	}
	public List<JBoltDatabaseTable> getAllTablesFromCache(JBoltDatasource jBoltDatasource){
		return getAllTablesFromCache(jBoltDatasource,null);
	}
	public List<JBoltDatabaseTable> getAllTablesFromCache(JBoltDatasource jBoltDatasource,Predicate<String> predicate){
		//if(JBoltConfig.JBOLT_CACHE_TYPE.equals(JBoltCacheType.EHCACHE)){
		//	return JBoltCacheKit.get(JBoltConfig.JBOLT_CACHE_NAME,"jbolt_tables_from_datasource_"+jBoltDatasource.getName(),()->{
		//		return getAllJBoltDatabaseTables(jBoltDatasource);
		//	});
		//}
		return JBoltCacheKit.get(JBoltConfig.JBOLT_CACHE_NAME,"jbolt_tables_from_datasource_"+jBoltDatasource.getName(),()->{
			return getAllJBoltDatabaseTables(jBoltDatasource);
		},60*60*8);
	}

	private List<JBoltDatabaseTable> getAllJBoltDatabaseTables(JBoltDatasource jBoltDatasource) {
		List<TableMeta> tableMetas = getAllTableMetas(jBoltDatasource);

		if(tableMetas!=null&&tableMetas.size()>0){
			List<JBoltDatabaseTable> tables = new ArrayList<>();
			JBoltDatabaseTable table;
			for(TableMeta tableMeta:tableMetas){
				table = convertToJBoltDatabaseTable(tableMeta);
				if(table!=null){
					tables.add(table);
				}
			}
			return tables;
		}
		return null;
	}

	private JBoltDatabaseTable convertToJBoltDatabaseTable(TableMeta tableMeta) {
		JBoltDatabaseTable table = new JBoltDatabaseTable();
		table.setName(tableMeta.name);
		table.setRemark(tableMeta.remarks);
		table.setPrimaryKey(tableMeta.primaryKey);
		Class clazz = JBoltModelConfig.me.getModelClassByTable(tableMeta.name);
		if(clazz!=null){
			table.setIsMappingModel(true);
			table.setJavaPackage(clazz.getPackage().getName());
			table.setModelName(clazz.getSimpleName());
			table.setBaseModelName("Base"+clazz.getSimpleName());
		}
		table.setColumns(convertToJBoltDatabaseColumns(tableMeta.columnMetas));
		return table;
	}

	private List<JBoltDatabaseColumn> convertToJBoltDatabaseColumns(List<ColumnMeta> columnMetas) {
		if(columnMetas == null || columnMetas.size() == 0){
			return null;
		}
		List<JBoltDatabaseColumn> columns = new ArrayList<>();
		JBoltDatabaseColumn column;
		for(ColumnMeta columnMeta:columnMetas){
			column = convertToJBoltDatabaseColumn(columnMeta);
			if(column!=null){
				columns.add(column);
			}
		}
		return columns;
	}

	private JBoltDatabaseColumn convertToJBoltDatabaseColumn(ColumnMeta col) {
		JBoltDatabaseColumn column = new JBoltDatabaseColumn();
		column.setName(col.name);
		column.setRemark(StrKit.defaultIfBlank(col.remarks, col.attrName));
		column.setAttrName(col.attrName);
		column.setJavaType(col.javaType);
		column.setType(col.type);
		column.setIsPrimaryKey(col.isPrimaryKey.equalsIgnoreCase("pri"));
		column.setIsRequired(!col.isNullable.equalsIgnoreCase("yes"));
		if(StrKit.notBlank(col.defaultValue)) {
			if(col.javaType.equalsIgnoreCase("java.lang.boolean")) {
				boolean isTrue = col.defaultValue.equalsIgnoreCase("true") || col.defaultValue.equalsIgnoreCase(Sql.TRUE);
				//属性默认值
				column.setDefaultValue(isTrue?Sql.TRUE:Sql.FALSE);
			}else {
				//属性默认值
				column.setDefaultValue(col.defaultValue);
			}
		}
		String len = ColumnTypeToDirective.getLength(col.type);
		if(StrKit.isBlank(len)||len.equals("0")) {
			column.setLength(0);
		}else {
			column.setLength(Integer.parseInt(len));
		}

		String fixed = ColumnTypeToDirective.getFixed(col.type);
		if(StrKit.isBlank(fixed)||fixed.equals("0")) {
			column.setFixed(0);
		}else {
			column.setFixed(Integer.parseInt(fixed));
		}
		return column;
	}

	private void removeAllTablesCache(String datasource){
		if(datasource!=null && datasource.trim().length()>0){
			JBoltCacheKit.remove(JBoltConfig.JBOLT_CACHE_NAME,"jbolt_tables_from_datasource_"+datasource);
		}
	}

	public void refreshAllTablesCache(String datasource){
		if(datasource!=null && datasource.trim().length()>0) {
			removeAllTablesCache(datasource);
			getAllTablesFromCache(datasource);
		}
	}

}
